﻿using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Double;
using SpiceNet.Circuits;

namespace SpiceNet.Integration
{
    /// <summary>
    /// The abstract definition of any integration method
    /// </summary>
    public abstract class IntegrationMethod
    {
        /// <summary>
        /// The result of an integration
        /// </summary>
        public class IntegrationResult
        {
            public double Geq = 0.0;
            public double Ceq = 0.0;
        }

        /// <summary>
        /// The step factor
        /// </summary>
        public double Step = 0.0;

        /// <summary>
        /// The maximum time step
        /// </summary>
        public double MaxStep = 0.0;

        /// <summary>
        /// Gets the breakpoints
        /// </summary>
        public Breakpoints Breaks { get; } = new Breakpoints();

        /// <summary>
        /// The simulation is at a breakpoint
        /// </summary>
        public bool Break = false;

        /// <summary>
        /// Gets the configuration for integration
        /// </summary>
        public IntegrationConfiguration Config { get; } = new IntegrationConfiguration();

        /// <summary>
        /// The current delta
        /// </summary>
        public double Delta = 0.0;

        /// <summary>
        /// Saved delta
        /// </summary>
        public double SaveDelta = 0.0;

        /// <summary>
        /// The past deltas
        /// </summary>
        public double[] DeltaOld = null;

        /// <summary>
        /// The time it needs to start recording
        /// </summary>
        public double InitTime = 0.0;

        /// <summary>
        /// The final time
        /// </summary>
        public double FinalTime = 0.0;

        /// <summary>
        /// Gets or sets the order for the integration method
        /// </summary>
        public int Order { get; set; } = 0;

        /// <summary>
        /// The maximum order for the integration method
        /// </summary>
        public abstract int MaxOrder { get; }

        /// <summary>
        /// The predicted solution vector
        /// </summary>
        public Vector<double> Pred { get; protected set; } = null;

        /// <summary>
        /// Previous solutions
        /// </summary>
        public Vector<double>[] Sols { get; protected set; } = null;

        /// <summary>
        /// Gets the derivation of a variable to the timestep
        /// </summary>
        public double Deriv { get; protected set; } = 0.0;

        /// <summary>
        /// Constructor
        /// </summary>
        public IntegrationMethod() { }

        /// <summary>
        /// Save a solution in the list of solutions
        /// </summary>
        /// <param name="solution"></param>
        public void SaveSolution(Vector<double> solution)
        {
            if (Sols[0] == null)
            {
                for (int i = 0; i < Sols.Length; i++)
                    Sols[i] = new DenseVector(solution.Count);
            }
            else
            {
                for (int i = Sols.Length - 2; i >= 0; i--)
                    Sols[i + 1] = Sols[i];
            }
            solution.CopyTo(Sols[0]);
        }

        /// <summary>
        /// Integrate a variable
        /// </summary>
        /// <param name="ckt"></param>
        /// <returns></returns>
        public abstract IntegrationResult Integrate(CircuitState state, int index, double cap);

        /// <summary>
        /// Truncate to relax the timestep if possible
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="delta"></param>
        /// <returns></returns>
        public abstract double Truncate(Circuit ckt);

        /// <summary>
        /// Predict a solution based on the current circuit state
        /// </summary>
        /// <param name="ckt"></param>
        public abstract void Predict(Circuit ckt);

        /// <summary>
        /// Compute any coefficients necessary to do the integration
        /// </summary>
        /// <param name="ckt"></param>
        public abstract void ComputeCoefficients(Circuit ckt);
    }
}
